/*
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is RhinoDB, released August 08, 2008.
 * 
 * Contributor(s): Alexandru Chiculita.
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 or later (the "GPL"), in which
 * case the provisions of the GPL are applicable instead of those above. If
 * you wish to allow use of your version of this file only under the terms of
 * the GPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replacing
 * them with the notice and other provisions required by the GPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the GPL.
 * 
 * ***** END LICENSE BLOCK ***** */

package org.achicu.db.values;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

import org.achicu.db.StorableValueFactory;
import org.achicu.db.StorageException;
import org.achicu.db.Store;

public class StorableObject extends AbstractStorableValue{
	
	public static final int TYPE = 1; 
	
	private int id;
	private boolean loaded = false;
	private HashMap properties = new HashMap();
	private boolean dirty = false;
	
	public boolean isDirty(){
		return dirty;
	}
	
	public StorableObject(int id){
		this.id = id;
	}
	
	public StorableObject(){
		id = -1;
	}
	
	public StorableObject(Store store){
		id = -1;
		this.store = store;
		dirty = false;
	}
	
	public StorableObject(Store store, int id){
		this.id = id;
		this.store = store;
		dirty = false;
	}
	
	
	public void putProperty(String key, StorableValue value) throws StorageException{
		checkLoaded();
		value.setStore(store);
		properties.put(key, value);
		setDirty( true );
	}
	
	public StorableValue getProperty(String key) throws StorageException{
		checkLoaded();
		return (StorableValue)properties.get(key);
	}
	
	public boolean hasProperty(String key) throws StorageException{
		checkLoaded();
		return properties.containsKey(key);
	}
	
	public StorableValue removeProperty(String key) throws StorageException{
		checkLoaded();
		return (StorableValue)properties.remove(key);
	}
	
	public int getId(){
		return id;
	}

	public void setId(int id){
		this.id = id;
	}
	
	
	public void readFrom(ObjectInputStream input) throws IOException, InstantiationException, IllegalAccessException, StorageException {
		//properties.clear();
		
		if(input!=null){
			//read the object id from the stream
			this.id = input.readInt();
		}
		
		if(id!=-1 && store!=null){
			byte[] buffer = store.getBufferById(id);
			if( buffer==null )
				throw new StorageException("Object " + id + " not found.");
			
			ObjectInputStream objInput = new ObjectInputStream(new ByteArrayInputStream(buffer));
			
			int count = objInput.readInt();
			
			while( count-- > 0 ){
				int propertyNameLen = objInput.readInt();
				byte [] propertyNameBuffer = new byte[propertyNameLen];
				
				int pos = 0;
				while(pos<propertyNameLen){
					pos+=objInput.read(propertyNameBuffer, pos, propertyNameLen-pos);
				}
				
				
				String propertyName = new String(propertyNameBuffer, "UTF-8");
				
				int propertyType = objInput.readInt();
				StorableValue value = StorableValueFactory.getInstance().getStorableValueByType(propertyType);
				if(value==null)
					throw new StorageException("Undefined property type "+propertyType);
				
				value.setStore(store);
				value.readFrom(objInput);
				
				properties.put(propertyName, value);
			}
			
			loaded = true;
			
			setDirty( false );
		}
		
	}

	
	public void writeTo(ObjectOutputStream output) throws IOException {
		if((dirty || this.id==-1) && store!=null){
			ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
			ObjectOutputStream objOutput = new ObjectOutputStream(byteArrayOutputStream);
			
			objOutput.writeInt( properties.size() );
			
			Iterator iter = properties.entrySet().iterator();
			
			while( iter.hasNext() ){
				Entry item = (Entry)iter.next();
				String propertyName = (String)item.getKey();
				StorableValue value = (StorableValue)item.getValue();
				
				byte [] buffer = propertyName.getBytes("UTF-8");
				objOutput.writeInt(buffer.length);
				objOutput.write(buffer);
				
				objOutput.writeInt(value.getType());
				
				value.setStore(store);
				
				value.writeTo(objOutput);
			}
			objOutput.flush();
			
			if(this.id==-1){
				this.id = store.saveBuffer( byteArrayOutputStream.toByteArray() );
			}else{
				this.id = store.rewriteBuffer( this.id, byteArrayOutputStream.toByteArray() );
			}
			
			setDirty( false );
		}
		
		if(output!=null){
			output.writeInt( this.id );
		}
		
	}
	
	public void saveToStore() throws StorageException{
		try {
			writeTo(null);
		} catch (Throwable e) {
			throw new StorageException(e);
		}
	}
	
	public void loadFromStore() throws StorageException{
		try {
			readFrom(null);
		} catch(StorageException e1){
			throw e1;
		} catch(Throwable e) {
			throw new StorageException(e);
		}
	}
	
	public String[] getProperties() throws StorageException{
		checkLoaded();
		return (String[])properties.keySet().toArray( new String[ properties.size() ] );
	}
	
	public void checkLoaded() throws StorageException{
		if(loaded)
			return;
		
		if(this.id==-1)
			return;
		
		try {
			loadFromStore();
		} catch(StorageException e){
			throw e;
		} catch (Throwable e) {
			throw new StorageException(e);
		}
	}
	
	
	public int getType() {
		return TYPE;
	}

	private void setDirty(boolean dirty){
		boolean wasDirty = this.dirty;
		this.dirty = dirty;
		
		if(store!=null){
			if(dirty&&!wasDirty){
				store.addDirtyObject(this);
			}else if (!dirty&&wasDirty){
				store.removeDirtyObject(this);
			}
		}
	}
	
	
	public void setStore(Store store){
		if(store!=this.store){
			this.store = store;
			setDirty( true );
		}
	}
	
	
	public Object unwrap() {
		return this;
	}
}
